<!DOCTYPE html>
<html><meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>java BIO、NIO、AIO</title>
<style type="text/css">
    body{
        background-color: #3C3F41;
    }
    a{
        text-decoration: none;
        color: #fbfbff;
    }
</style>
<body><head>
    <style type="text/css">
        nav ul {
            list-style-type: none;
            margin: 0;
            padding: 0;
            overflow: hidden;
            background-color: #333;
            position: -webkit-sticky;  
            position: sticky;
            top: 0;
        }

        nav li {
            float: left;
        }

        nav li a {
            display: block;
            color: white;
            text-align: center;
            padding: 14px 16px;
            text-decoration: none;
        }

         
        nav li a:hover {
            background-color: #111;
        }
        .active {
            background-color: #4CAF50;
        }
    </style>
</head>

<nav>
    <ul>
        
        
        <li><a href="/blog/">home</a></li>
         
        
        <li><a href="/blog/post">博文</a></li>
         
        
        <li><a href="/blog/about">about</a></li>
         
    </ul>
</nav>
<div id="content" style="margin: auto;width: 50%;padding: 10px; color: #ffffff;height: 100%">
    <article>
        <header>
            <h1 class="post-title">java BIO、NIO、AIO</h1>
        </header>
        <p>带着问题去学习</p>
<ol>
<li>
<p>BIO、NIO、AIO 的区别是什么？</p>
</li>
<li>
<p>同/异步、阻/非阻塞的区别是什么？</p>
</li>
<li>
<p>文件读写最优雅的实现方式是什么？</p>
</li>
<li>
<p>NIO 如何实现多路复用功能？</p>
</li>
</ol>
<p>一、IO 介绍</p>
<p>I/O输入/输出(Input/Output)，分为IO设备和IO接口两个部分。</p>
<p><strong>BIO、NIO、AIO的区别</strong></p>
<p>BIO 就是传统的 java.io 包，它是基于流模型实现的，交互的方式是<strong>同步、阻塞</strong>方式。<strong>优点:</strong> 就是代码比较简单、直观；<strong>缺点:</strong> IO 的效率和扩展性很低，容易成为应用性能瓶颈。</p>
<p>NIO 是 Java 1.4 引入的 java.nio 包，提供了 Channel、Selector、Buffer 等新的抽象，可以构建<strong>多路复用的、同步非阻塞</strong> IO 程序，同时提供了<strong>更接近操作系统底层</strong>高性能的数据操作方式。<strong>优点:</strong> ；<strong>缺点:</strong> 。</p>
<p>AIO 是 Java 1.7 之后引入的包，是 NIO 的升级版本，提供了异步非堵塞的 IO 操作方式，所以人们叫它 AIO（Asynchronous IO），异步 IO 是基于事件和回调机制实现的，也就是应用操作之后会直接返回，不会堵塞在那里，当后台处理完成，操作系统会通知相应的线程进行后续的操作。</p>
<p>NIO和IO的主要区别</p>
<p>下表总结了Java IO和NIO之间的主要区别：</p>
<table>
<thead>
<tr>
<th style="text-align:center">组合方式</th>
<th style="text-align:left">性能分析</th>
<th style="text-align:left">&ndash;</th>
<th style="text-align:left">优点</th>
<th style="text-align:left">缺点</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">IO</td>
<td style="text-align:left">面向流</td>
<td style="text-align:left">同步 阻塞</td>
<td style="text-align:left">代码简单 直观</td>
<td style="text-align:left">性能低 资源浪费 不易扩展</td>
</tr>
<tr>
<td style="text-align:center">NIO</td>
<td style="text-align:left">面向缓冲 多路复用</td>
<td style="text-align:left">同步 非阻塞</td>
<td style="text-align:left">高性能 易扩展</td>
<td style="text-align:left">代码实现比较复杂,需要对多线程</td>
</tr>
<tr>
<td style="text-align:center">AIO</td>
<td style="text-align:left">面向缓冲 多路复用</td>
<td style="text-align:left">异步 非阻塞</td>
<td style="text-align:left">windows下高性能,不同操作系统不同表现</td>
<td style="text-align:left">底层封装,不易扩展</td>
</tr>
</tbody>
</table>
<p>二、同步、异步、阻塞、非阻塞</p>
<p>上面说了很多关于同步、异步、阻塞和非阻塞的概念，接下来就具体聊一下它们4个的含义，以及组合之后形成的性能分析。</p>
<p>2.1 同步与异步</p>
<p>同步就是一个任务的完成需要依赖另外一个任务时，只有等待被依赖的任务完成后，依赖的任务才能算完成，这是一种可靠的任务序列。要么成功都成功，失败都失败，两个任务的状态可以保持一致。而异步是不需要等待被依赖的任务完成，只是通知被依赖的任务要完成什么工作，依赖的任务也立即执行，只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成，依赖它的任务无法确定，所以它是不可靠的任务序列。我们可以用打电话和发短信来很好的比喻同步与异步操作。</p>
<p>2.2 阻塞与非阻塞</p>
<p>阻塞与非阻塞主要是从 CPU 的消耗上来说的，阻塞就是 CPU 停下来等待一个慢的操作完成 CPU 才接着完成其它的事。非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事，等这个慢的操作完成时，CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率，但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。</p>
<p>2.3 同/异、阻/非堵塞 组合</p>
<p>同/异、阻/非堵塞的组合，有四种类型，如下表：</p>
<table>
<thead>
<tr>
<th style="text-align:center">组合方式</th>
<th style="text-align:left">性能分析</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">同步阻塞</td>
<td style="text-align:left">最常用的一种用法，使用也是最简单的，但是 I/O 性能一般很差，CPU 大部分在空闲状态。</td>
</tr>
<tr>
<td style="text-align:center">同步非阻塞</td>
<td style="text-align:left">提升 I/O 性能的常用手段，就是将 I/O 的阻塞改成非阻塞方式，尤其在网络 I/O 是长连接，同时传输数据也不是很多的情况下，提升性能非常有效。 这种方式通常能提升 I/O 性能，但是会增加CPU 消耗，要考虑增加的 I/O 性能能不能补偿 CPU 的消耗，也就是系统的瓶颈是在 I/O 还是在 CPU 上。</td>
</tr>
<tr>
<td style="text-align:center">异步阻塞</td>
<td style="text-align:left">这种方式在分布式数据库中经常用到，例如在网一个分布式数据库中写一条记录，通常会有一份是同步阻塞的记录，而还有两至三份是备份记录会写到其它机器上，这些备份记录通常都是采用异步阻塞的方式写 I/O。异步阻塞对网络 I/O 能够提升效率，尤其像上面这种同时写多份相同数据的情况。</td>
</tr>
<tr>
<td style="text-align:center">异步非阻塞</td>
<td style="text-align:left">这种组合方式用起来比较复杂，只有在一些非常复杂的分布式情况下使用，像集群之间的消息同步机制一般用这种 I/O 组合方式。如 Cassandra 的 Gossip 通信机制就是采用异步非阻塞的方式。它适合同时要传多份相同的数据到集群中不同的机器，同时数据的传输量虽然不大，但是却非常频繁。这种网络 I/O 用这个方式性能能达到最高。</td>
</tr>
</tbody>
</table>
<p>优雅的文件读写
Java 7 之前文件的读取是这样的：</p>
<pre tabindex="0"><code>// 添加文件
FileWriter fileWriter = new FileWriter(filePath, true);
fileWriter.write(Content);
fileWriter.close();

// 读取文件
FileReader fileReader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(fileReader);
StringBuffer bf = new StringBuffer();
String str;
while ((str = bufferedReader.readLine()) != null) {
    bf.append(str + &#34;\n&#34;);
}
bufferedReader.close();
fileReader.close();
System.out.println(bf.toString());
Java 7 引入了Files（java.nio包下）的，大大简化了文件的读写，如下：

// 写入文件（追加方式：StandardOpenOption.APPEND）
Files.write(Paths.get(filePath), Content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);

// 读取文件
byte[] data = Files.readAllBytes(Paths.get(filePath));
System.out.println(new String(data, StandardCharsets.UTF_8));
读写文件都是一行代码搞定，没错这就是最优雅的文件操作。

Files 下还有很多有用的方法，比如创建多层文件夹，写法上也简单了：

// 创建多（单）层目录（如果不存在创建，存在不会报错）
new File(&#34;D://a//b&#34;).mkdirs();
</code></pre><p>四、Socket 和 NIO 的多路复用
本节带你实现最基础的 Socket 的同时，同时会实现 NIO 多路复用，还有 AIO 中 Socket 的实现。</p>
<p>4.1 传统的 Socket 实现
接下来我们将会实现一个简单的 Socket，服务器端只发给客户端信息，再由客户端打印出来的例子，代码如下：</p>
<pre tabindex="0"><code>int port = 4343; //端口号
// Socket 服务器端（简单的发送信息）
Thread sThread = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while (true) {
                // 等待连接
                Socket socket = serverSocket.accept();
                Thread sHandlerThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try (PrintWriter printWriter = new PrintWriter(socket.getOutputStream())) {
                            printWriter.println(&#34;hello world！&#34;);
                            printWriter.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                sHandlerThread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
sThread.start();

// Socket 客户端（接收信息并打印）
try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) {
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
    bufferedReader.lines().forEach(s -&gt; System.out.println(&#34;客户端：&#34; + s));
} catch (UnknownHostException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
</code></pre><p>调用 accept 方法，阻塞等待客户端连接；
利用 Socket 模拟了一个简单的客户端，只进行连接、读取和打印；
在 Java 中，线程的实现是比较重量级的，所以线程的启动或者销毁是很消耗服务器的资源的，即使使用线程池来实现，使用上述传统的 Socket 方式，当连接数极具上升也会带来性能瓶颈，原因是线程的上线文切换开销会在高并发的时候体现的很明显，并且以上操作方式还是同步阻塞式的编程，性能问题在高并发的时候就会体现的尤为明显。</p>
<p>以上的流程，如下图：</p>
<p>4.2 NIO 多路复用
介于以上高并发的问题，NIO 的多路复用功能就显得意义非凡了。</p>
<p>NIO 是利用了单线程轮询事件的机制，通过高效地定位就绪的 Channel，来决定做什么，仅仅 select 阶段是阻塞的，可以有效避免大量客户端连接时，频繁线程切换带来的问题，应用的扩展能力有了非常大的提高。</p>
<pre tabindex="0"><code>// NIO 多路复用
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 4,
        60L, TimeUnit.SECONDS, new LinkedBlockingQueue&lt;Runnable&gt;());
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) {
            serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), port));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                selector.select(); // 阻塞等待就绪的Channel
                Set&lt;SelectionKey&gt; selectionKeys = selector.selectedKeys();
                Iterator&lt;SelectionKey&gt; iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    try (SocketChannel channel = ((ServerSocketChannel) key.channel()).accept()) {
                        channel.write(Charset.defaultCharset().encode(&#34;你好，世界&#34;));
                    }
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});

// Socket 客户端（接收信息并打印）
try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) {
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
    bufferedReader.lines().forEach(s -&gt; System.out.println(&#34;NIO 客户端：&#34; + s));
} catch (IOException e) {
    e.printStackTrace();
}
</code></pre><p>首先，通过 Selector.open() 创建一个 Selector，作为类似调度员的角色；
然后，创建一个 ServerSocketChannel，并且向 Selector 注册，通过指定 SelectionKey.OP_ACCEPT，告诉调度员，它关注的是新的连接请求；
为什么我们要明确配置非阻塞模式呢？这是因为阻塞模式下，注册操作是不允许的，会抛出 IllegalBlockingModeException 异常；
Selector 阻塞在 select 操作，当有 Channel 发生接入请求，就会被唤醒；
下面的图，可以有效的说明 NIO 复用的流程：</p>
<p>就这样 NIO 的多路复用就大大提升了服务器端响应高并发的能力。</p>
<p>4.3 AIO 版 Socket 实现
Java 1.7 提供了 AIO 实现的 Socket 是这样的，如下代码：</p>
<pre tabindex="0"><code>// AIO线程复用版
Thread sThread = new Thread(new Runnable() {
    @Override
    public void run() {
        AsynchronousChannelGroup group = null;
        try {
            group = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(4));
            AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(InetAddress.getLocalHost(), port));
            server.accept(null, new CompletionHandler&lt;AsynchronousSocketChannel, AsynchronousServerSocketChannel&gt;() {
                @Override
                public void completed(AsynchronousSocketChannel result, AsynchronousServerSocketChannel attachment) {
                    server.accept(null, this); // 接收下一个请求
                    try {
                        Future&lt;Integer&gt; f = result.write(Charset.defaultCharset().encode(&#34;你好，世界&#34;));
                        f.get();
                        System.out.println(&#34;服务端发送时间：&#34; + new SimpleDateFormat(&#34;yyyy-MM-dd HH:mm:ss&#34;).format(new Date()));
                        result.close();
                    } catch (InterruptedException | ExecutionException | IOException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) {
                }
            });
            group.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
});
sThread.start();

// Socket 客户端
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
Future&lt;Void&gt; future = client.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));
future.get();
ByteBuffer buffer = ByteBuffer.allocate(100);
client.read(buffer, null, new CompletionHandler&lt;Integer, Void&gt;() {
    @Override
    public void completed(Integer result, Void attachment) {
        System.out.println(&#34;客户端打印：&#34; + new String(buffer.array()));
    }

    @Override
    public void failed(Throwable exc, Void attachment) {
        exc.printStackTrace();
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
Thread.sleep(10 * 1000);
</code></pre><p>五、总结</p>
<p>以上基本就是 IO 从 1.0 到目前版本（本文的版本）JDK 8 的核心使用操作了，可以看出来 IO 作为比较常用的基础功能，发展变化的改动也很大，而且使用起来也越来越简单了，IO 的操作也是比较好理解的，一个输入一个输出，掌握好了输入输出也就掌握好了 IO，Socket 作为网络交互的集成功能，显然 NIO 的多路复用，给 Socket 带来了更多的活力和选择，用户可以根据自己的实际场景选择相应的代码策略。</p>
<p>六、参考</p>
<p><a href="https://my.oschina.net/u/3471412/blog/2966696">https://my.oschina.net/u/3471412/blog/2966696</a></p>

        <br />

        <div>
            
            <a href="https://alittlesmile.gitee.io/blog/post/computer/ide%E7%A1%AC%E7%9B%98%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93%E6%A8%A1%E5%BC%8F-/">&lt; java 读写文件</a>
            

            
            <a href="https://alittlesmile.gitee.io/blog/post/hexo/hexo/" style="float:right">Hexo ></a>
            
        </div>
    </article>

</div></body>
</html>
